/*
 * ClientCommands.cpp
 *
 *  Created on: 14 mei 2013
 *      Author: maarten
 */

#include "ClientCommands.hpp"
#include "CommandCodes.hpp"
#include "Logger.hpp"

#include "string"
#include <sstream>
#include <sys/socket.h>
#include <iostream>			//cout
#include <cstdio>			//perror
#include <cstring>			//memset
#include <unistd.h>			//read
#include <cstdlib>			//exit
#include <fcntl.h>			//file opening
#include <sys/stat.h>		//fstat
#include <cerrno>			//errno
#include <fstream>			//fstream
#include <sstream>			//sstream

#include <fcntl.h>      	//open()
#include <unistd.h>     	//read()
#include <sys/stat.h>   	//chmod()

#include <sys/time.h>

/*
 * This class manages all the commands the client can perform.
 * A socket is given later to guide the actions to given server.
*/
ClientCommands::ClientCommands() {
	socketDescriptor = 0;
	HANIntern = false;
	messageCode = 0;
}

void ClientCommands::setSocketDescriptor(int sd) {
	socketDescriptor = sd;
}

bool ClientCommands::getHANIntern(){
	return HANIntern;
}

ClientCommands::~ClientCommands() {
}

/*
 * Requests the server to logon to it.
 * Sends a logon request and process the response.
 * HANIntern and extentions are set here.
 */
int ClientCommands::requestLogon(std::string username, std::string password) {
	//request logon
	Message sMessage = Message();
	sMessage.setHeader(REQUEST_LOGON);
	sMessage.addBody(username);
	sMessage.addBody(password);
	Logger::getInstance().log(sMessage);
	send(socketDescriptor, sMessage.getCompleteMessage(), sMessage.getCompleteMessageSize(), 0);

	//wait and process response
	Message rMessage = waitForResponse();
	std::vector<std::string> rMessageBody = rMessage.parseBody();
	if ((messageCode = checkMessageCode(rMessage.getHeader(), RESPONSE_LOGON)) == 0) {
		if (rMessageBody.size() > 1) {
			HANIntern = !!atoi(rMessageBody.at(1).c_str());
			extensions = parser.parse(rMessageBody.at(2), ",");
		} else {
			Logger::getInstance().log("No login parameters were given from the server.");
			return -1;
		}
	} else {
		return messageCode;
	}
	return 0;

}

/*
 * Requests the server to upload a file to it.
 */
int ClientCommands::requestUpload(std::string filePath, std::string fileTargetPath) {
	//get filename from path
	std::string filename = filePath;
	const size_t last_slash_idx = filename.find_last_of("\\/");
	if (std::string::npos != last_slash_idx)
	{
	    filename.erase(0, last_slash_idx + 1);
	}

	//open file
	FILE* pFile;
	if ((pFile = fopen(filePath.c_str(), "r")) == NULL) {
		Logger::getInstance().log("Could not open the file, upload aborted.");
		return ERROR_OPEN_FILE;
	}

	//get filesize from file
	int fileSize = 0;
	fseek(pFile, 0, SEEK_END);
	fileSize = ftell(pFile);
	rewind(pFile);

	//send upload request with filename and size
	Message sMessageRUP = Message();
	sMessageRUP.setHeader(REQUEST_UPLOAD_PREPARE);
	std::string target = fileTargetPath;
	target += filename;
	sMessageRUP.addBody(target);
	std::stringstream ss;
	ss << fileSize;
	std::string str = ss.str();
	sMessageRUP.addBody(str);
	Logger::getInstance().log(sMessageRUP);
	send(socketDescriptor, sMessageRUP.getCompleteMessage(), sMessageRUP.getCompleteMessageSize(), 0);

	//wait and process response
	Message rMessage = waitForResponse();
	if ((messageCode = checkMessageCode(rMessage.getHeader(), RESPONSE_UPLOAD_PREPARE)) == 0) {
		char buffer[FILESPLIT + 1];
		memset(buffer, '\0', FILESPLIT + 1);

		//for every split of the file, send it to the server
		int fileSplits = (fileSize / FILESPLIT) + 1;
		for (int i = 0; i < fileSplits; i++) {
			//read file part
			int readSize = fread(buffer, 1, FILESPLIT, pFile);

			//send file part
			Message sMessageRUT = Message();
			sMessageRUT.setHeader(REQUEST_UPLOAD_TRANSFER);
			sMessageRUT.addBody(buffer, readSize);
			memset(buffer, '\0', FILESPLIT + 1);
			Logger::getInstance().log(sMessageRUT);
			send(socketDescriptor, sMessageRUT.getCompleteMessage(), sMessageRUT.getCompleteMessageSize(), 0);

			//wait and process response
			Message rMessage2 = waitForResponse();
			if ((messageCode = checkMessageCode(rMessage2.getHeader(), RESPONSE_UPLOAD_TRANSFER)) == 0) {
			} else {
				fclose(pFile);
				return messageCode;
			}
		}

		//close file
		fclose(pFile);

		//send closing message
		Message sMessageRUC = Message();
		sMessageRUC.setHeader(REQUEST_UPLOAD_COMPLETE);
		Logger::getInstance().log(sMessageRUC);
		send(socketDescriptor, sMessageRUC.getCompleteMessage(), sMessageRUC.getCompleteMessageSize(), 0);

		//receive closing message
		Message rMessage3 = waitForResponse();
		if ((messageCode = checkMessageCode(rMessage3.getHeader(), RESPONSE_UPLOAD_COMPLETE)) != 0) {
			return messageCode;
		}
	} else {
		return messageCode;
	}
	return 0;
}

/*
 * Requests the server to download a given file from it.
 */
int ClientCommands::requestDownload(std::string filename, std::string path) {
	//get filename from path
	std::string name = filename;
	const size_t last_slash_idx = name.find_last_of("\\/");
	if (std::string::npos != last_slash_idx) {
		name.erase(0, last_slash_idx + 1);
	}
	//change working dir to givenpath
	path += name;

	//open/create file
	Logger::getInstance().log(path);
	int file = 0;
	if ((file = open(path.c_str(), O_CREAT | O_WRONLY, 0700)) == -1) {
		Logger::getInstance().log("file cannot be read");
		return ERROR_OPEN_FILE;
	}
	chmod(path.c_str(), 0700);

	//send file name to download
	Message sMessageRDP = Message();
	sMessageRDP.setHeader(REQUEST_DOWNLOAD_PREPARE);
	sMessageRDP.addBody(filename);
	Logger::getInstance().log(sMessageRDP);
	send(socketDescriptor, sMessageRDP.getCompleteMessage(), sMessageRDP.getCompleteMessageSize(), 0);

	//wait and process response
	Message rMessage = waitForResponse();

	//check messagecode
	if ((messageCode = checkMessageCode(rMessage.getHeader(), RESPONSE_DOWNLOAD_PREPARE)) != 0) {
		close(file);
		remove(path.c_str());
		return messageCode;
	}

	//send request download message
	Message sMessageRDT = Message();
	sMessageRDT.setHeader(REQUEST_DOWNLOAD_TRANSFER);
	Logger::getInstance().log(sMessageRDT);
	send(socketDescriptor, sMessageRDT.getCompleteMessage(), sMessageRDT.getCompleteMessageSize(), 0);

	//wait and process response
	rMessage = waitForResponse();

	//for each message received, write it contents in the file, ask server if there are more.
	while (rMessage.getHeader() == RESPONSE_DOWNLOAD_TRANSFER) {

		if (write(file, rMessage.getBody(), rMessage.getBodyLength()) != rMessage.getBodyLength()) {
			Logger::getInstance().log("An error occured while writing.");
			return ERROR_WRITE_FILE;
		}

		Logger::getInstance().log(sMessageRDT);
		send(socketDescriptor, sMessageRDT.getCompleteMessage(), sMessageRDT.getCompleteMessageSize(), 0);

		//wait and process response
		rMessage = waitForResponse();
	}

	if ((messageCode = checkMessageCode(rMessage.getHeader(), RESPONSE_DOWNLOAD_TRANSFERCOMPLETE)) != 0) {
		return messageCode;
	}

	if (close(file) < 0) {
		Logger::getInstance().log("An error occurred while closing the file.");
		return ERROR_CLOSE_FILE;
	}

	return 0;
}

/*
 * Requests the server to delete a specific file or directory.
 */
int ClientCommands::requestDelete(std::string file) {
	//send request for delete
	Message sMessage = Message();
	sMessage.setHeader(REQUEST_DELETE_FILE);
	sMessage.addBody(file);
	Logger::getInstance().log(sMessage);
	send(socketDescriptor, sMessage.getCompleteMessage(), sMessage.getCompleteMessageSize(), 0);

	//wait and process response
	Message rMessage = waitForResponse();

	//check and print
	if ((messageCode = checkMessageCode(rMessage.getHeader(), RESPONSE_DELETE_FILE)) == 0) {
	} else {
		return messageCode;
	}
	return 0;
}

/*
 * Requests the server to rename a specific file or directory.
 */
int ClientCommands::requestRename(std::string oldName, std::string newName) {
	//check extension
	int punt = newName.find_last_of(".");
	bool filenameAllowed = false;

	for (unsigned int i = 0; i < extensions.size(); i++) {
		if (newName.substr(punt + 1).compare(extensions.at(i)) == 0) {
			filenameAllowed = true;
			break;
		}
	}
	if (!filenameAllowed){
		return ERROR_FILE_EXTENTION;
	}

	//send request for rename
	Message sMessage = Message();
	sMessage.setHeader(REQUEST_RENAME_FILE);
	sMessage.addBody(oldName);
	sMessage.addBody(newName);
	Logger::getInstance().log(sMessage);
	send(socketDescriptor, sMessage.getCompleteMessage(), sMessage.getCompleteMessageSize(), 0);

	//wait and process response
	Message rMessage = waitForResponse();

	//check and print
	if ((messageCode = checkMessageCode(rMessage.getHeader(), RESPONSE_RENAME_FILE)) == 0) {
	} else {
		return messageCode;
	}
	return 0;
}

/*
 * Requests the server to add a directory.
 */
int ClientCommands::requestAddDir(std::string dirName) {
	//send request for addDir
	Message sMessage = Message();
	sMessage.setHeader(REQUEST_ADD_FOLDER);
	sMessage.addBody(dirName);
	Logger::getInstance().log(sMessage);
	send(socketDescriptor, sMessage.getCompleteMessage(), sMessage.getCompleteMessageSize(), 0);

	//wait and process response
	Message rMessage = waitForResponse();

	//check and print
	if ((messageCode = checkMessageCode(rMessage.getHeader(), RESPONSE_ADD_FOLDER)) == 0) {
	} else {
		return messageCode;
	}
	return 0;
}

/*
 * Requests the containments of the directory in the server.
 */
std::vector<std::string> ClientCommands::requestDirStructure(std::string dir) {
	//send request for dirstructure
	Message sMessage = Message();
	sMessage.setHeader(REQUEST_OVERVIEW);
	sMessage.addBody(dir);
	Logger::getInstance().log(sMessage);
	send(socketDescriptor, sMessage.getCompleteMessage(), sMessage.getCompleteMessageSize(), 0);

	//wait and process response
	Message rMessage = waitForResponse();

	std::vector<std::string> response;
	//check and print
	if (checkMessageCode(rMessage.getHeader(), RESPONSE_OVERVIEW) == 0) {
		response = rMessage.parseBody();
		return response;
	} else {
		std::stringstream ss;
		ss << rMessage.getHeader();
		std::string str = ss.str();
		response.push_back(str);
		return response;
	}
}

/*
 * Get directories from the containments of the vector.
 */
std::vector<std::string> ClientCommands::getDirs(std::vector<std::string> dirStruct) {
	std::vector<std::string> dirs = parser.parse(dirStruct.at(1), ",");
	return dirs;
}

/*
 * Get files from the containments of the vector.
 */
std::vector<std::string> ClientCommands::getFiles(std::vector<std::string> dirStruct) {
	std::vector<std::string> files = parser.parse(dirStruct.at(2), ",");
	return files;
}

/*
 * Wait for the response of the server and process this into a message.
 */
Message ClientCommands::waitForResponse() {
	//wait for response
	int read_size = 0;
	char buffer[BUFFERSIZE];
	memset(buffer, '\0', BUFFERSIZE);
	if ((read_size = read(socketDescriptor, buffer, BUFFERSIZE)) > 0) {
	}

	//Check for errors or disconnection of the server.
	if (read_size == 0) {
		Logger::getInstance().log("Server disconnected");
		Message eMessage = Message();
		eMessage.setHeader(0);
		return eMessage;
	} else if (read_size == -1) {
		Logger::getInstance().log("Received failed");
		Message eMessage = Message();
		eMessage.setHeader(0);
		return eMessage;
	}

	char buffer2[read_size];
	memset(buffer2, '\0', read_size);
	memcpy(buffer2, buffer, read_size);

	Message rMessage = Message();
	rMessage.setHeader(buffer2[0]);
	rMessage.addBody(buffer2 +2, read_size-2);

	Logger::getInstance().log(rMessage);
	return rMessage;
}

/*
 * Checks if the given messagecode was the expected code, if not an error will be logged.
 */
int ClientCommands::checkMessageCode(int code, int expectedCode) {
	if (code == expectedCode) {
		return 0;
	}
	switch (code) {
	case 0:
		Logger::getInstance().log("Server disconnected.");
		return code;
	case ERROR_LOGIN_FAIL:
		Logger::getInstance().log("ERROR_LOGIN_FAIL: Username or password is incorrect.");
		return code;
	case ERROR_LOGIN_NOTIDENTIFIED:
		Logger::getInstance().log("ERROR_LOGIN_NOTIDENTIFIED: You are not logged in.");
		return code;
	case ERROR_FILE_NOTPERMITTED:
		Logger::getInstance().log("ERROR_FILE_NOTPERMITTED: You are not permitted to do this action.");
		return code;
	case ERROR_FILE_FAIL:
		Logger::getInstance().log("ERROR_FILE_FAIL: Something went wrong when accessing the file.");
		return code;
	case ERROR_FILE_ISOPEN:
		Logger::getInstance().log("ERROR_FILE_ISOPEN: File is already open.");
		return code;
	case ERROR_FILE_NOFILE:
		Logger::getInstance().log("ERROR_FILE_NOFILE: No file given.");
		return code;
	case ERROR_FILE_EXTENTION:
		Logger::getInstance().log("ERROR_FILE_EXTENTION: Given extension is not allowed.");
		return code;
	case ERROR_FILE_SIZE:
		Logger::getInstance().log("ERROR_FILE_SIZE: File is too big.");
		return code;
	default:
		std::stringstream error;
		error << "An unknown error was given: " << code << " expected: " << expectedCode;
		Logger::getInstance().log(error.str());
		return code;
	}
}
